﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Text;
using System.Threading.Actors.Languages;
using System.Threading.Actors.Utilities;
using System.Threading.Tasks;

namespace System.Threading.Actors
{
    // IActorState implementation that simply stores state in memory.
    // This implementation will not be tolerant of service cycling.
    public class InMemoryActorState : IActorState
    {
        private readonly string m_serviceName;
        private readonly MethodCallHelper m_methodHelper;
        private readonly PubSubHelper m_pubSubHelper;
        private readonly AssemblyHelper m_assemblyHelper;
        private readonly IActorInteractor m_actorInteractionHelper;
        private readonly Dictionary<string, ChangeOperation> m_transactionBuffer = new Dictionary<string, ChangeOperation>(); // ops for current transaction
        private readonly ConcurrentDictionary<string, object> m_permaStore = new ConcurrentDictionary<string, object>();
        private ActorETWEventListener m_ETWListener;
        private static readonly Task s_preCompletedTask = Task.FromResult(true);
        private readonly SemaphoreSlim m_asyncLock = new SemaphoreSlim(1, 1);


        public InMemoryActorState(string serviceName, IActorInteractor actorInteractor)
        {
            Contract.Requires(serviceName != null);
            //Contract.Requires(actorInteractor != null);  // Sorta required, but the place we use this in a unit test can't supply one.

            // Kick off real-time logging of Actor ETW events
            // TODO: Push ActorETWEventListener creation down to PubSubHelper?
            m_ETWListener = new ActorETWEventListener(serviceName);
            m_ETWListener.EnableEvents(ActorETWEventSource.Log, EventLevel.Informational);

            m_serviceName = serviceName;
            m_methodHelper = new MethodCallHelper(this);
            m_pubSubHelper = new PubSubHelper(this, null, m_ETWListener);
            m_assemblyHelper = new AssemblyHelper(this, m_permaStore, m_methodHelper);
            m_assemblyHelper.AddAssemblyResolver();
            m_actorInteractionHelper = actorInteractor;
            ActorProxyBase.RegisterState(m_serviceName, null, this);
        }

        public SemaphoreSlim AsyncStateLock { get { return m_asyncLock; } }

        public void Dispose()
        {
            if (m_assemblyHelper != null) m_assemblyHelper.RemoveAssemblyResolver();
            if (m_serviceName != null) ActorProxyBase.UnregisterState(m_serviceName, null);
            if (m_pubSubHelper != null) m_pubSubHelper.Close();
            if (m_actorInteractionHelper != null) m_actorInteractionHelper.Dispose();
        }

        public string PartitionName {
            get { return null; }  // No partitioning support for now.
        }

        public void Set(string key, object value)
        {
            m_transactionBuffer[key] = ChangeOperation.FromSet(key, value);
        }

        public object Get(string key)
        {
            object result;
            if (!TryGet(key, out result))
            {
                throw new KeyNotFoundException(String.Format("Could not find key \"{0}\"", key));
            }

            return result;
        }

        public bool TryGet(string key, out object value)
        {
            ChangeOperation transactionBufferOp;
            if (m_transactionBuffer.TryGetValue(key, out transactionBufferOp))
            {
                if (transactionBufferOp.OperationType == ChangeOperationType.Set)
                {
                    value = transactionBufferOp.OperationValue;
                    return true;
                }
                else if (transactionBufferOp.OperationType == ChangeOperationType.Remove)
                {
                    value = null;
                    return false;
                }
                else throw new InvalidOperationException("Unsupported ChangeOperationType");
            }
            else return m_permaStore.TryGetValue(key, out value);
        }

        public void Remove(string key)
        {
            m_transactionBuffer[key] = ChangeOperation.FromRemove(key);
        }

        public Task Flush()
        {
            foreach (var kvp in m_transactionBuffer)
            {
                if (kvp.Value.OperationType == ChangeOperationType.Set)
                {
                    if(!m_assemblyHelper.CheckForAssemblyOrMethod(kvp.Key, kvp.Value.OperationValue, true))
                    {
                        m_permaStore[kvp.Key] = kvp.Value.OperationValue;
                    }
                }
                else if (kvp.Value.OperationType == ChangeOperationType.Remove)
                {
                    object temp;
                    m_permaStore.TryRemove(kvp.Key, out temp);
                }
                else throw new InvalidOperationException("Unknown ChangeOperationType");
            }
            m_transactionBuffer.Clear();
            return s_preCompletedTask;
        }

        public bool ChangesPendingForCurrentOperation()
        {
            return (m_transactionBuffer.Count > 0);
        }

        public void ClearChangesForCurrentOperation()
        {
            m_transactionBuffer.Clear();
        }

        public IEnumerable<string> GetAllKeysWithPrefix(string keyPrefix)
        {
            HashSet<string> hits = new HashSet<string>();
            var prefixLen = keyPrefix.Length;

            foreach (var kvp in m_permaStore)
            {
                if (kvp.Key.StartsWith(keyPrefix)) hits.Add(kvp.Key.Substring(prefixLen));
            }
            foreach (var kvp in m_transactionBuffer)
            {
                if (kvp.Key.StartsWith(keyPrefix))
                {
                    var shortName = kvp.Key.Substring(prefixLen);
                    if (kvp.Value.OperationType == ChangeOperationType.Set)
                    {
                        hits.Add(shortName); // will just return false if it was already there
                    }
                    else if (kvp.Value.OperationType == ChangeOperationType.Remove)
                    {
                        hits.Remove(shortName); // will just return false if it is not there
                    }
                    else throw new InvalidOperationException("Unknown ChangeOperationType");
                }
            }

            return hits;
        }

        public object CallMethod(string methodName, object[] args)
        {
            return m_methodHelper.CallMethod(methodName, args);
        }

        public void Subscribe(IPublicationCallbackContract callerProxy, string eventType)
        {
            m_pubSubHelper.Subscribe(callerProxy, eventType);
        }

        public void Unsubscribe(IPublicationCallbackContract callerProxy, string eventType)
        {
            m_pubSubHelper.Unsubscribe(callerProxy, eventType);
        }

        public void UnsubscribeAll(IPublicationCallbackContract callerProxy)
        {
            if (callerProxy == null)
                m_pubSubHelper.UnsubscribeEverything();
            else
                m_pubSubHelper.UnsubscribeAll(callerProxy);
        }

        public void Publish(string eventType, object eventPayload)
        {
            Publish(eventType, eventPayload, null);
        }

        public void Publish(string eventType, object eventPayload, params object[] eventPayloadQualifiers)
        {
            m_pubSubHelper.Publish(eventType, eventPayload, eventPayloadQualifiers);
        }

        public IActorProxy GetActorProxy(string actorName, string partitionName = null)
        {
            Contract.Assert(m_actorInteractionHelper != null, "You must pass in an IActorInteractor in the constructor to InMemoryActorState.");
            return m_actorInteractionHelper.GetActorProxy(actorName, this, partitionName, "");
        }

        public string GetId()
        {
            return m_serviceName;
        }

        public void AddLanguagePack(SupportedLanguages language, Func<ILanguagePackage> pack)
        {
            m_methodHelper.AddLanguagePack(language, pack);
        }

        public void AddMethod(SupportedLanguages language, string methodName, string methodImpl)
        {
            m_methodHelper.AddMethod(language, methodName, methodImpl);
        }

        public bool CreateActor(string actorName, string creationString, int numPartitions = 1)
        {
            Contract.Assert(m_actorInteractionHelper != null, "You must pass in an IActorInteractor in the constructor to InMemoryActorState.");
            return m_actorInteractionHelper.CreateActor(actorName, creationString, numPartitions);
        }

        public void DeleteActor(string actorName)
        {
            Contract.Assert(m_actorInteractionHelper != null, "You must pass in an IActorInteractor in the constructor to InMemoryActorState.");
            m_actorInteractionHelper.DeleteActor(actorName);
        }

        public void Configure(string configurationString)
        {
            ConfigurationHelper.Configure(configurationString, m_ETWListener);
        }

        public IActorPromise GetActorProxyAsync(string externalActorName, string partitionName = null)
        {
            throw new NotImplementedException();
        }
    }
}
